{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "0fcbe797-ab19-4d25-97ee-43f3e76c9424",
   "metadata": {},
   "source": [
    "# 检索增强生成（Retrieval Augmented Generation, RAG）第 1 部分：  \n",
    "## 构建一个利用你自己的文档来辅助生成回答的应用程序。\n",
    "\n",
    "### 构建一个 **检索增强生成（RAG）** 应用：第 1 部分  \n",
    "LLM（大语言模型）所赋能的最强大应用之一是**高级问答（Q&A）聊天机器人**。这类应用能够回答与特定信息源相关的问题，其背后使用的技术被称为 **检索增强生成（Retrieval Augmented Generation, RAG）**。\n",
    "\n",
    "这是一个多部分教程：\n",
    "\n",
    "- **第 1 部分（本指南）**：介绍 RAG 基本概念，并逐步演示一个最小可行实现。\n",
    "- **第 2 部分**：扩展实现功能，支持对话式交互和多步骤检索流程。\n",
    "\n",
    "本教程将展示如何基于文本数据源构建一个简单的问答应用程序。\n",
    "\n",
    "---\n",
    "\n",
    "## 概览\n",
    "\n",
    "一个典型的 RAG 应用通常包含两个主要组成部分：\n",
    "\n",
    "#### 1. 索引（Indexing）\n",
    "这是用于从原始数据源中加载并建立索引的流程，通常在离线状态下执行。\n",
    "\n",
    "#### 2. 检索与生成（Retrieval and Generation）\n",
    "即真正的 RAG 流程：在运行时接收用户查询，从索引中检索相关信息，并将这些信息传给模型进行内容生成。\n",
    "\n",
    "---\n",
    "\n",
    "### 📌 索引阶段（Indexing）\n",
    "\n",
    "#### 1. 加载（Load）\n",
    "首先我们需要加载数据，这一步通常通过 **Document Loaders（文档加载器）** 来完成。\n",
    "\n",
    "#### 2. 分割（Split）\n",
    "接下来，使用 **文本分割器（Text Splitters）** 将大块文档切分为较小的片段。这样做有两个好处：\n",
    "- 更容易进行索引和搜索；\n",
    "- 适配模型有限的上下文窗口（context window），避免超长文本无法处理。\n",
    "\n",
    "#### 3. 存储（Store）\n",
    "我们需要一个地方来存储并索引这些小片段，以便后续可以进行检索。这通常使用 **向量数据库（VectorStore）** 和 **嵌入模型（Embeddings model）** 来实现。\n",
    "\n",
    "![示例图片](../../assets/imgs/rag_indexing.png)  "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "915f354c-dd7a-4ad2-ad97-bf2099886fd6",
   "metadata": {},
   "source": [
    "### 检索与生成（Retrieval and Generation）\n",
    "\n",
    "#### 4. 检索（Retrieve）  \n",
    "当用户提供输入时，系统会使用 **检索器（Retriever）** 从存储中找出与其相关的文档片段（即之前分割并嵌入的“chunks”）。\n",
    "\n",
    "#### 5. 生成（Generate）  \n",
    "接下来，**聊天模型（ChatModel）或大语言模型（LLM）** 会基于一个包含用户问题和检索结果的提示词（prompt），生成最终的回答。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bf7aadc3-c6d9-49f9-b379-6e344ac0974f",
   "metadata": {},
   "source": [
    "![示例图片](../../assets/imgs/rag_retrieval_generation.png) \n",
    "\n",
    "在我们完成数据的索引处理后，将使用 **LangGraph** 作为编排框架来实现**检索与生成**的整个流程。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "93c3dac1-8105-4c6b-97bd-64383fb984c2",
   "metadata": {},
   "outputs": [],
   "source": [
    "# %pip install --quiet --upgrade langchain-text-splitters langchain-community langgraph"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "a2a8355b-28f0-4c4d-b7d1-55c00f5e078a",
   "metadata": {},
   "outputs": [],
   "source": [
    "import getpass\n",
    "import os\n",
    "\n",
    "try:\n",
    "    # load environment variables from .env file (requires `python-dotenv`)\n",
    "    from dotenv import load_dotenv\n",
    "\n",
    "    _ = load_dotenv()\n",
    "except ImportError:\n",
    "    pass\n",
    "\n",
    "if not os.environ.get(\"DASHSCOPE_API_KEY\"):\n",
    "  os.environ[\"DASHSCOPE_API_KEY\"] = getpass.getpass(\"Enter API key for OpenAI: \")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "85da6f81-f7af-4409-8052-f3af4d59c1e3",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_community.chat_models.tongyi import ChatTongyi\n",
    "\n",
    "llm  = ChatTongyi(\n",
    "    streaming=True,\n",
    "    name=\"qwen-turbo\"\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "379e5cbb-ccdb-4d42-8b8d-06a977d6cbe8",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_community.embeddings.dashscope import DashScopeEmbeddings\n",
    "# 初始化 Qwen Embedding 模型\n",
    "embeddings = DashScopeEmbeddings(model=\"text-embedding-v1\")  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "b67c7e25-7bb7-4eef-9452-701db091700e",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.vectorstores import InMemoryVectorStore\n",
    "\n",
    "vector_store = InMemoryVectorStore(embeddings)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "442a9607-b0ea-46de-ad77-cb7eb5db296b",
   "metadata": {},
   "source": [
    "### 预览\n",
    "\n",
    "在本指南中，我们将构建一个能够回答**网站内容相关问题**的应用程序。我们使用的具体网页是 [**中小企业出海对接交流会**](https://gyxxh.tj.gov.cn/glllm/gabsycs/gxdtgh/202504/t20250408_6902492.html)，这将使我们能够针对该文章的内容提出各种问题。\n",
    "\n",
    "我们将通过一个简单的索引流程和 RAG 链来实现这一功能，整个实现仅需大约 **50 行代码** 即可完成。\n",
    "\n",
    "### 详细解析\n",
    "\n",
    "接下来，我们将**逐步实现**，帮助你真正理解每一步究竟发生了什么。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3f62d56a-7579-4e96-b058-cfea15132bb7",
   "metadata": {},
   "source": [
    "### 1. 索引（Indexing）\n",
    "\n",
    "#### 加载文档（Loading Documents）\n",
    "\n",
    "我们首先需要加载博客文章的内容。为此，我们可以使用 **DocumentLoaders** 工具。这些工具可以从指定的数据源加载数据，并将其返回为一个 `Document` 对象的列表。\n",
    "\n",
    "在本例中，我们将使用 **WebBaseLoader**，它通过 `urllib` 从网页 URL 加载 HTML 内容，并使用 **BeautifulSoup** 将 HTML 解析为纯文本。\n",
    "\n",
    "我们可以通过 `bs_kwargs` 参数自定义 HTML 到文本的解析过程（具体可参考 [BeautifulSoup 文档](https://www.crummy.com/software/BeautifulSoup/bs4/doc/)）。  \n",
    "在这个例子中，只有包含 `class=\"view-wrap\"` 的 HTML 标签是相关的，因此我们会移除其他所有标签。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "47b4c745-fe83-46d7-8aed-d229bfc6e65e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total characters: 1065\n"
     ]
    }
   ],
   "source": [
    "import bs4\n",
    "from langchain_community.document_loaders import WebBaseLoader\n",
    "\n",
    "# Only keep post title, headers, and content from the full HTML.\n",
    "bs4_strainer = bs4.SoupStrainer(class_=(\"view-wrap\"))\n",
    "loader = WebBaseLoader(\n",
    "    web_paths=(\"https://gyxxh.tj.gov.cn/glllm/gabsycs/gxdtgh/202504/t20250408_6902492.html\",),\n",
    "    bs_kwargs={\"parse_only\": bs4_strainer},\n",
    ")\n",
    "docs = loader.load()\n",
    "\n",
    "assert len(docs) == 1\n",
    "print(f\"Total characters: {len(docs[0].page_content)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "c1449a8a-0015-4ce9-a1d4-acca1545d297",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\n",
      "\n",
      "\n",
      "\n",
      "\n",
      "\n",
      "\n",
      "\n",
      "            工信动态\n",
      "          \n",
      "\n",
      "\n",
      "\n",
      "\n",
      "\n",
      "        天津市中小企业服务中心成功举办轻工 产业链中小企业出海对接交流会\n",
      "      \n",
      "\n",
      "来源：\n",
      "          天津市工业和信息化局\n",
      "        \n",
      "发布时间：\n",
      "          2025-04-08 09:30\n",
      "        \n",
      "\n",
      "\n",
      "为深入贯彻党的二十届三中全会精神，落实《工业和信息化部办公厅关于开展中小企业出海服务专项行动的通知》及《工业和信息化部等17部门办公厅（室）关于开展2025年“一起益企”中小企业服务行动的通知》要求，进一步提升我市中小企业国际化水平和核心竞争力，助力中小企业开拓国际市场、增强风险防控能力，天津市中小企业服务中心于近日成功举办轻工产业链中小企业出海对接交流会。中国中小企业发展促进中心、天津市贸促会、天津市食品工业协会、天津市自行车电动车行业协会、武清区崔黄口镇商会，以及来自自行车、食品、地毯等领域的生产制造企业和进出口企业等20余家单位代表参加了活动。  会上，国家中心产业集群发展研究所负责同志围绕中小企业“走出去”万帆耘海行动的实施背景、主要任务及具体\n"
     ]
    }
   ],
   "source": [
    "print(docs[0].page_content[:500])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a1481e99-499f-4154-9270-222a082e6769",
   "metadata": {},
   "source": [
    "### 深入理解\n",
    "\n",
    "---\n",
    "\n",
    "#### 文档加载器（DocumentLoader）  \n",
    "**定义**：一个从数据源加载数据的对象，返回的是一个 `Document` 对象的列表。\n",
    "\n",
    "---\n",
    "\n",
    "#### 分割文档（Splitting Documents）\n",
    "\n",
    "我们已经加载的文档长度超过 **42,000 个字符**，这远远超出了许多语言模型上下文窗口的限制。即使某些模型能够容纳整篇内容，在非常长的输入中查找信息仍然会变得困难。\n",
    "\n",
    "为了解决这个问题，我们将整个文档分割成多个小块（chunks），以便进行嵌入（embedding）和向量存储（vector storage）。这样在运行时检索时，系统只需获取与用户问题最相关的部分内容。\n",
    "\n",
    "我们采用 **RecursiveCharacterTextSplitter**。该工具会递归地尝试使用常见的分隔符（如换行符、空格等）对文档进行切分，直到每个文本块（chunk）的大小符合指定要求为止。\n",
    "\n",
    "这是推荐用于通用文本场景的文本分割器。\n",
    "\n",
    "---\n",
    "\n",
    "### 示例代码：\n",
    "\n",
    "```python\n",
    "from langchain.text_splitter import RecursiveCharacterTextSplitter\n",
    "\n",
    "text_splitter = RecursiveCharacterTextSplitter(\n",
    "    chunk_size=1000,   # 每个文本块的最大字符数\n",
    "    chunk_overlap=150  # 块之间的重叠字符数，保持上下文连贯性\n",
    ")\n",
    "\n",
    "docs = text_splitter.split_documents(documents)\n",
    "```\n",
    "\n",
    "通过这种方式，我们可以将一篇长文章拆分成多个较小的段落，便于后续处理和高效检索。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "a422522b-a53a-4c1e-9e35-2f63f6730636",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Split blog post into 2 sub-documents.\n"
     ]
    }
   ],
   "source": [
    "from langchain_text_splitters import RecursiveCharacterTextSplitter\n",
    "\n",
    "text_splitter = RecursiveCharacterTextSplitter(\n",
    "    chunk_size=1000,  # chunk size (characters)\n",
    "    chunk_overlap=200,  # chunk overlap (characters)\n",
    "    add_start_index=True,  # track index in original document\n",
    ")\n",
    "all_splits = text_splitter.split_documents(docs)\n",
    "\n",
    "print(f\"Split blog post into {len(all_splits)} sub-documents.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f32e9294-ec8f-4620-840a-563646765d54",
   "metadata": {},
   "source": [
    "### TextSplitter（文本分割器）：  \n",
    "**定义**：一个将 `Document` 列表拆分为更小文本块（chunks）的对象。它是 `DocumentTransformer` 的子类。\n",
    "\n",
    "---\n",
    "\n",
    "### DocumentTransformer（文档转换器）：  \n",
    "**定义**：对 `Document` 对象列表执行某种转换操作的对象。\n",
    "\n",
    "---\n",
    "\n",
    "### 存储文档（Storing Documents）\n",
    "\n",
    "现在我们需要对我们切分后的 **66 个文本块**建立索引，以便在运行时可以对其进行搜索。\n",
    "\n",
    "我们的方法是：\n",
    "\n",
    "1. 使用嵌入模型（Embeddings model）对每个文本块的内容进行向量化；\n",
    "2. 将这些向量插入到一个 **向量数据库（Vector Store）** 中。\n",
    "\n",
    "这样，当用户提供查询时，我们就可以使用**向量相似性搜索**来检索最相关的文档内容。\n",
    "\n",
    "---\n",
    "\n",
    "### 实现方式\n",
    "\n",
    "我们可以使用在本教程开始时选择的 **向量数据库（VectorStore）** 和 **嵌入模型（Embeddings Model）**，通过一条命令完成所有文档片段的嵌入与存储。\n",
    "\n",
    "示例代码如下：\n",
    "\n",
    "```python\n",
    "from langchain.vectorstores import Chroma\n",
    "from langchain.embeddings import OpenAIEmbeddings\n",
    "\n",
    "vectorstore = Chroma.from_documents(\n",
    "    documents=docs,\n",
    "    embedding=OpenAIEmbeddings()\n",
    ")\n",
    "```\n",
    "\n",
    "这段代码会自动为每一个文本块生成嵌入向量，并将其保存到向量数据库中，供后续检索使用。\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "91a6f4f7-7b43-4018-973b-1c774a603587",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['5b857693-630f-4f4d-b8f6-7f20f8749da7', 'cb23ba32-ad51-419d-b3ea-5af544b1208b']\n"
     ]
    }
   ],
   "source": [
    "document_ids = vector_store.add_documents(documents=all_splits)\n",
    "\n",
    "print(document_ids[:3])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "70332fa8-9a4d-43f4-88eb-da65ac31548e",
   "metadata": {},
   "source": [
    "### 嵌入模型（Embeddings）  \n",
    "**定义**：用于将文本转换为嵌入向量（embeddings）的模型封装器（wrapper）。\n",
    "\n",
    "---\n",
    "\n",
    "### 向量数据库（VectorStore）  \n",
    "**定义**：对向量数据库的封装，用于存储和查询嵌入向量。\n",
    "\n",
    "---\n",
    "\n",
    "至此，我们完成了整个**索引阶段（Indexing）**。现在我们已经建立了一个可以被查询的向量数据库，其中包含我们博客文章的分块内容。当用户提供一个问题时，理想情况下我们可以返回博客中能够回答该问题的相关片段。\n",
    "\n",
    "---\n",
    "\n",
    "## 2. 检索与生成（Retrieval and Generation）\n",
    "\n",
    "现在我们来编写实际的应用逻辑。\n",
    "\n",
    "我们希望构建一个简单的应用程序，它能够：\n",
    "\n",
    "1. 接收用户的提问；\n",
    "2. 根据问题搜索相关的文档内容；\n",
    "3. 将检索到的文档内容和原始问题一起传给语言模型；\n",
    "4. 最终返回一个结构清晰、准确的回答。\n",
    "\n",
    "在**生成（Generate）**阶段，我们将使用本教程开始时所选择的 **聊天模型（Chat Model）** 来生成最终答案。\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "93266468-856f-4145-9859-db92dbe45bd9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Use the following pieces of context to answer the question at the end.\n",
      "If you don't know the answer, just say that you don't know, don't try to make up an answer.\n",
      "Use three sentences maximum and keep the answer as concise as possible.\n",
      "Always say \"thanks for asking!\" at the end of the answer.\n",
      "\n",
      "(context goes here)\n",
      "\n",
      "Question: (question goes here)\n",
      "\n",
      "Helpful Answer:\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.prompts import PromptTemplate\n",
    "\n",
    "template = \"\"\"Use the following pieces of context to answer the question at the end.\n",
    "If you don't know the answer, just say that you don't know, don't try to make up an answer.\n",
    "Use three sentences maximum and keep the answer as concise as possible.\n",
    "Always say \"thanks for asking!\" at the end of the answer.\n",
    "\n",
    "{context}\n",
    "\n",
    "Question: {question}\n",
    "\n",
    "Helpful Answer:\"\"\"\n",
    "prompt = PromptTemplate.from_template(template)\n",
    "\n",
    "example_messages = prompt.invoke(\n",
    "    {\"context\": \"(context goes here)\", \"question\": \"(question goes here)\"}\n",
    ").to_messages()\n",
    "\n",
    "assert len(example_messages) == 1\n",
    "print(example_messages[0].content)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e29bb427-9f3b-4aa9-a2ef-06d5c41c3c9e",
   "metadata": {},
   "source": [
    "我们将使用 **LangGraph** 将检索（retrieval）和生成（generation）步骤整合到一个应用程序中。这样做可以带来以下诸多优势：\n",
    "\n",
    "- 我们可以**一次性定义应用逻辑**，并自动支持多种调用模式，包括流式传输（streaming）、异步调用（async）和批量调用（batched calls）。\n",
    "- 通过 **LangGraph 平台**，我们可以实现更高效的部署。\n",
    "- **LangSmith** 将自动追踪应用程序的各个步骤，便于调试与优化。\n",
    "- 我们可以**轻松地为应用添加关键功能**，例如状态持久化（persistence）和人工审核（human-in-the-loop approval），而且几乎不需要大幅修改代码。\n",
    "\n",
    "---\n",
    "\n",
    "### 使用 LangGraph 需要定义三个核心部分：\n",
    "\n",
    "#### 1. **State（状态）**\n",
    "控制应用程序的输入数据、各步骤之间的数据传递以及输出结果。  \n",
    "它通常是一个 `TypedDict`，也可以是一个 `Pydantic BaseModel`。\n",
    "\n",
    "#### 2. **Nodes（节点）**\n",
    "代表应用程序中的各个步骤（即函数或操作），例如：加载问题、检索文档、生成答案等。\n",
    "\n",
    "#### 3. **Control Flow（控制流）**\n",
    "定义这些节点的执行顺序，比如“先检索再生成”。\n",
    "\n",
    "---\n",
    "\n",
    "### State（状态）详解\n",
    "\n",
    "对于一个简单的 RAG 应用程序，我们只需要跟踪以下三项数据：\n",
    "\n",
    "```python\n",
    "from typing import TypedDict\n",
    "\n",
    "class State(TypedDict):\n",
    "    question: str      # 用户的问题\n",
    "    context: str       # 检索到的相关内容\n",
    "    answer: str        # 最终生成的答案\n",
    "```\n",
    "\n",
    "这个状态对象将在整个流程中贯穿始终，作为每一步操作的数据输入和输出载体。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "86dbb3b0-edf1-493a-8ad1-983b2cb0d322",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.documents import Document\n",
    "from typing_extensions import List, TypedDict\n",
    "\n",
    "\n",
    "class State(TypedDict):\n",
    "    question: str\n",
    "    context: List[Document]\n",
    "    answer: str"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6a804168-942f-4c69-b192-8ff173a41507",
   "metadata": {},
   "source": [
    "### 节点（Nodes）——应用程序的各个步骤\n",
    "\n",
    "我们先从一个包含两个步骤的简单流程开始：**检索（retrieval）** 和 **生成（generation）**。\n",
    "\n",
    "这两个步骤分别对应：\n",
    "\n",
    "1. **检索（Retrieval）**：根据用户的问题，从向量数据库中查找相关的文档内容。\n",
    "2. **生成（Generation）**：将检索到的内容和原始问题一起输入给语言模型（LLM），由它生成最终的回答。\n",
    "\n",
    "在 LangGraph 中，每个步骤都被定义为一个“节点”（Node），这些节点是函数形式的组件，接收当前的状态（State），并返回更新后的状态。通过这种方式，我们可以清晰地组织整个应用的逻辑流程。\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "3bf4140a-6392-48e5-bce1-486abb883b24",
   "metadata": {},
   "outputs": [],
   "source": [
    "def retrieve(state: State):\n",
    "    retrieved_docs = vector_store.similarity_search(state[\"question\"])\n",
    "    return {\"context\": retrieved_docs}\n",
    "\n",
    "\n",
    "def generate(state: State):\n",
    "    docs_content = \"\\n\\n\".join(doc.page_content for doc in state[\"context\"])\n",
    "    messages = prompt.invoke({\"question\": state[\"question\"], \"context\": docs_content})\n",
    "    response = llm.invoke(messages)\n",
    "    return {\"answer\": response.content}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bc43d8ce-c163-4194-b498-4827b5e541d2",
   "metadata": {},
   "source": [
    "### 我们的检索与生成步骤\n",
    "\n",
    "#### 检索步骤（Retrieval Step）  \n",
    "我们的检索步骤非常简单：使用用户的输入问题，在向量数据库中执行**相似性搜索**，找到最相关的文档内容（context）。\n",
    "\n",
    "#### 生成步骤（Generation Step）  \n",
    "生成步骤则将检索到的上下文和原始问题一起格式化为一个提示词（prompt），然后传给聊天模型（Chat Model），由模型生成自然语言的回答。\n",
    "\n",
    "---\n",
    "\n",
    "### 控制流（Control Flow）\n",
    "\n",
    "最后，我们将整个应用程序编译成一个 **Graph（图）对象**。  \n",
    "在本例中，我们只是将“检索”和“生成”两个步骤按顺序连接起来，形成一个简单的流程：\n",
    "\n",
    "```\n",
    "[Retrieval] → [Generation]\n",
    "```\n",
    "\n",
    "这样，LangGraph 就可以根据我们定义的状态（State）、节点（Nodes）和控制流（Control Flow），自动管理数据在各个步骤之间的传递和更新。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "b5856f07-872e-47b4-a481-faad0eca7005",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langgraph.graph import START, StateGraph\n",
    "\n",
    "graph_builder = StateGraph(State).add_sequence([retrieve, generate])\n",
    "graph_builder.add_edge(START, \"retrieve\")\n",
    "graph = graph_builder.compile()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "fdeacc25-ab0e-4f67-ac7d-be60f48a832b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+-----------+  \n",
      "| __start__ |  \n",
      "+-----------+  \n",
      "      *        \n",
      "      *        \n",
      "      *        \n",
      "+----------+   \n",
      "| retrieve |   \n",
      "+----------+   \n",
      "      *        \n",
      "      *        \n",
      "      *        \n",
      "+----------+   \n",
      "| generate |   \n",
      "+----------+   \n",
      "      *        \n",
      "      *        \n",
      "      *        \n",
      " +---------+   \n",
      " | __end__ |   \n",
      " +---------+   \n"
     ]
    }
   ],
   "source": [
    "print(graph.get_graph().draw_ascii())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "be956511-29b5-4bbe-8a1d-df1fb782e72d",
   "metadata": {},
   "source": [
    "### 使用（Usage）\n",
    "\n",
    "让我们来测试一下我们构建的应用程序！  \n",
    "**LangGraph 支持多种调用方式**，包括：\n",
    "\n",
    "- 同步调用（sync）\n",
    "- 异步调用（async）\n",
    "- 流式调用（streaming）\n",
    "\n",
    "#### 调用（Invoke）示例：\n",
    "\n",
    "你可以使用 `.invoke()` 方法以同步方式运行你的 RAG 应用：\n",
    "\n",
    "```python\n",
    "response = app.invoke({\"question\": \"文章中提到的自主智能体的核心组件有哪些？\"})\n",
    "print(response[\"answer\"])\n",
    "```\n",
    "\n",
    "这会触发整个流程：\n",
    "\n",
    "1. 使用问题进行文档检索；\n",
    "2. 将检索结果和问题传给语言模型；\n",
    "3. 返回生成的答案。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "60048170-e023-4eaa-a3a6-c24053cc396e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Context: [Document(id='6a34df01-e4f1-4fc4-bff5-ac409b37b82e', metadata={'source': 'https://gyxxh.tj.gov.cn/glllm/gabsycs/gxdtgh/202504/t20250408_6902492.html'}, page_content='来源：\\n          天津市工业和信息化局\\n        \\n发布时间：\\n          2025-04-08 09:30\\n        \\n\\n\\n为深入贯彻党的二十届三中全会精神，落实《工业和信息化部办公厅关于开展中小企业出海服务专项行动的通知》及《工业和信息化部等17部门办公厅（室）关于开展2025年“一起益企”中小企业服务行动的通知》要求，进一步提升我市中小企业国际化水平和核心竞争力，助力中小企业开拓国际市场、增强风险防控能力，天津市中小企业服务中心于近日成功举办轻工产业链中小企业出海对接交流会。中国中小企业发展促进中心、天津市贸促会、天津市食品工业协会、天津市自行车电动车行业协会、武清区崔黄口镇商会，以及来自自行车、食品、地毯等领域的生产制造企业和进出口企业等20余家单位代表参加了活动。 \\u2002会上，国家中心产业集群发展研究所负责同志围绕中小企业“走出去”万帆耘海行动的实施背景、主要任务及具体进展作了专题报告，深入解读了当前中小企业出海的趋势、政策支持及发展机遇，为参会企业提供了权威的政策指导和行动路径。市贸促会会展服务中心相关负责同志重点介绍了贸促会组织的境外展会项目及我市境外参展补贴政策，并结合热点国家的贸易环境、市场动态及政策导向，为企业提供了详实的出海信息及参展支持服务，助力企业精准把握国际市场机遇。活动主题引起与会企业的高度关注，现场交流、对接气氛热烈。近年来，在国家大力推进构建以国内大循环为主体、国内国际双循环相互促进的新发展格局背景下，越来越多的中小企业将海外业务视为推动企业增长的重要战略。但在全球化经营过程中，中小企业面临诸多挑战，由于缺乏出海的实战经验，境外抗风险能力较低，减缓了我市企业出海的步伐。市中小企业服务中心2024年起已着手为有出海需求的企业搭建资源共享、经验互通的平台；2025年中心启动的“中小企业出海系列活动”涵盖出海对接交流会、国际市场调研、境外展会参展支持等多元化服务。下一步，中心将聚焦中小企业出海需求，深化与各行业协会及专业服务机构的协同合作，完善中小企业出海服务体系，整合资源系统化提供政策解读、市场开拓、国际人才引进、管理提升、跨境金融、权益保护、数字化转型及检测认证等关键节点的服务对接，助力企业提升国际化能力。推动我市中小企业在全球市场中形成竞争新优势。'), Document(id='97fb659a-dc71-4642-bb3d-1326275a9ca4', metadata={'source': 'https://gyxxh.tj.gov.cn/glllm/gabsycs/gxdtgh/202504/t20250408_6902492.html'}, page_content='来源：\\n          天津市工业和信息化局\\n        \\n发布时间：\\n          2025-04-08 09:30\\n        \\n\\n\\n为深入贯彻党的二十届三中全会精神，落实《工业和信息化部办公厅关于开展中小企业出海服务专项行动的通知》及《工业和信息化部等17部门办公厅（室）关于开展2025年“一起益企”中小企业服务行动的通知》要求，进一步提升我市中小企业国际化水平和核心竞争力，助力中小企业开拓国际市场、增强风险防控能力，天津市中小企业服务中心于近日成功举办轻工产业链中小企业出海对接交流会。中国中小企业发展促进中心、天津市贸促会、天津市食品工业协会、天津市自行车电动车行业协会、武清区崔黄口镇商会，以及来自自行车、食品、地毯等领域的生产制造企业和进出口企业等20余家单位代表参加了活动。 \\u2002会上，国家中心产业集群发展研究所负责同志围绕中小企业“走出去”万帆耘海行动的实施背景、主要任务及具体进展作了专题报告，深入解读了当前中小企业出海的趋势、政策支持及发展机遇，为参会企业提供了权威的政策指导和行动路径。市贸促会会展服务中心相关负责同志重点介绍了贸促会组织的境外展会项目及我市境外参展补贴政策，并结合热点国家的贸易环境、市场动态及政策导向，为企业提供了详实的出海信息及参展支持服务，助力企业精准把握国际市场机遇。活动主题引起与会企业的高度关注，现场交流、对接气氛热烈。近年来，在国家大力推进构建以国内大循环为主体、国内国际双循环相互促进的新发展格局背景下，越来越多的中小企业将海外业务视为推动企业增长的重要战略。但在全球化经营过程中，中小企业面临诸多挑战，由于缺乏出海的实战经验，境外抗风险能力较低，减缓了我市企业出海的步伐。市中小企业服务中心2024年起已着手为有出海需求的企业搭建资源共享、经验互通的平台；2025年中心启动的“中小企业出海系列活动”涵盖出海对接交流会、国际市场调研、境外展会参展支持等多元化服务。下一步，中心将聚焦中小企业出海需求，深化与各行业协会及专业服务机构的协同合作，完善中小企业出海服务体系，整合资源系统化提供政策解读、市场开拓、国际人才引进、管理提升、跨境金融、权益保护、数字化转型及检测认证等关键节点的服务对接，助力企业提升国际化能力。推动我市中小企业在全球市场中形成竞争新优势。'), Document(id='cb23ba32-ad51-419d-b3ea-5af544b1208b', metadata={'source': 'https://gyxxh.tj.gov.cn/glllm/gabsycs/gxdtgh/202504/t20250408_6902492.html', 'start_index': 92}, page_content='来源：\\n          天津市工业和信息化局\\n        \\n发布时间：\\n          2025-04-08 09:30\\n        \\n\\n\\n为深入贯彻党的二十届三中全会精神，落实《工业和信息化部办公厅关于开展中小企业出海服务专项行动的通知》及《工业和信息化部等17部门办公厅（室）关于开展2025年“一起益企”中小企业服务行动的通知》要求，进一步提升我市中小企业国际化水平和核心竞争力，助力中小企业开拓国际市场、增强风险防控能力，天津市中小企业服务中心于近日成功举办轻工产业链中小企业出海对接交流会。中国中小企业发展促进中心、天津市贸促会、天津市食品工业协会、天津市自行车电动车行业协会、武清区崔黄口镇商会，以及来自自行车、食品、地毯等领域的生产制造企业和进出口企业等20余家单位代表参加了活动。 \\u2002会上，国家中心产业集群发展研究所负责同志围绕中小企业“走出去”万帆耘海行动的实施背景、主要任务及具体进展作了专题报告，深入解读了当前中小企业出海的趋势、政策支持及发展机遇，为参会企业提供了权威的政策指导和行动路径。市贸促会会展服务中心相关负责同志重点介绍了贸促会组织的境外展会项目及我市境外参展补贴政策，并结合热点国家的贸易环境、市场动态及政策导向，为企业提供了详实的出海信息及参展支持服务，助力企业精准把握国际市场机遇。活动主题引起与会企业的高度关注，现场交流、对接气氛热烈。近年来，在国家大力推进构建以国内大循环为主体、国内国际双循环相互促进的新发展格局背景下，越来越多的中小企业将海外业务视为推动企业增长的重要战略。但在全球化经营过程中，中小企业面临诸多挑战，由于缺乏出海的实战经验，境外抗风险能力较低，减缓了我市企业出海的步伐。市中小企业服务中心2024年起已着手为有出海需求的企业搭建资源共享、经验互通的平台；2025年中心启动的“中小企业出海系列活动”涵盖出海对接交流会、国际市场调研、境外展会参展支持等多元化服务。下一步，中心将聚焦中小企业出海需求，深化与各行业协会及专业服务机构的协同合作，完善中小企业出海服务体系，整合资源系统化提供政策解读、市场开拓、国际人才引进、管理提升、跨境金融、权益保护、数字化转型及检测认证等关键节点的服务对接，助力企业提升国际化能力。推动我市中小企业在全球市场中形成竞争新优势。'), Document(id='cda2301b-c67f-48c1-afe1-7e6e950e728d', metadata={'source': 'https://gyxxh.tj.gov.cn/glllm/gabsycs/gxdtgh/202504/t20250408_6902492.html'}, page_content='工信动态\\n          \\n\\n\\n\\n\\n\\n        天津市中小企业服务中心成功举办轻工 产业链中小企业出海对接交流会\\n      \\n\\n来源：\\n          天津市工业和信息化局\\n        \\n发布时间：\\n          2025-04-08 09:30')]\n",
      "\n",
      "\n",
      "Answer: 约有20余家企业参加了此次活动。这些企业来自自行车、食品、地毯等领域，包括生产制造企业和进出口企业。具体提到的单位代表来自不同的行业协会和相关机构。\n"
     ]
    }
   ],
   "source": [
    "result = graph.invoke({\"question\": \"有多少家企业参加活动?\"})\n",
    "\n",
    "print(f'Context: {result[\"context\"]}\\n\\n')\n",
    "print(f'Answer: {result[\"answer\"]}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22031829-2433-4f65-bf47-735b9adeedcb",
   "metadata": {},
   "source": [
    "### 流式调用步骤（Stream Steps）："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "1218e378-3e0c-4427-9d78-846254d90cf5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'retrieve': {'context': [Document(id='6a34df01-e4f1-4fc4-bff5-ac409b37b82e', metadata={'source': 'https://gyxxh.tj.gov.cn/glllm/gabsycs/gxdtgh/202504/t20250408_6902492.html'}, page_content='来源：\\n          天津市工业和信息化局\\n        \\n发布时间：\\n          2025-04-08 09:30\\n        \\n\\n\\n为深入贯彻党的二十届三中全会精神，落实《工业和信息化部办公厅关于开展中小企业出海服务专项行动的通知》及《工业和信息化部等17部门办公厅（室）关于开展2025年“一起益企”中小企业服务行动的通知》要求，进一步提升我市中小企业国际化水平和核心竞争力，助力中小企业开拓国际市场、增强风险防控能力，天津市中小企业服务中心于近日成功举办轻工产业链中小企业出海对接交流会。中国中小企业发展促进中心、天津市贸促会、天津市食品工业协会、天津市自行车电动车行业协会、武清区崔黄口镇商会，以及来自自行车、食品、地毯等领域的生产制造企业和进出口企业等20余家单位代表参加了活动。 \\u2002会上，国家中心产业集群发展研究所负责同志围绕中小企业“走出去”万帆耘海行动的实施背景、主要任务及具体进展作了专题报告，深入解读了当前中小企业出海的趋势、政策支持及发展机遇，为参会企业提供了权威的政策指导和行动路径。市贸促会会展服务中心相关负责同志重点介绍了贸促会组织的境外展会项目及我市境外参展补贴政策，并结合热点国家的贸易环境、市场动态及政策导向，为企业提供了详实的出海信息及参展支持服务，助力企业精准把握国际市场机遇。活动主题引起与会企业的高度关注，现场交流、对接气氛热烈。近年来，在国家大力推进构建以国内大循环为主体、国内国际双循环相互促进的新发展格局背景下，越来越多的中小企业将海外业务视为推动企业增长的重要战略。但在全球化经营过程中，中小企业面临诸多挑战，由于缺乏出海的实战经验，境外抗风险能力较低，减缓了我市企业出海的步伐。市中小企业服务中心2024年起已着手为有出海需求的企业搭建资源共享、经验互通的平台；2025年中心启动的“中小企业出海系列活动”涵盖出海对接交流会、国际市场调研、境外展会参展支持等多元化服务。下一步，中心将聚焦中小企业出海需求，深化与各行业协会及专业服务机构的协同合作，完善中小企业出海服务体系，整合资源系统化提供政策解读、市场开拓、国际人才引进、管理提升、跨境金融、权益保护、数字化转型及检测认证等关键节点的服务对接，助力企业提升国际化能力。推动我市中小企业在全球市场中形成竞争新优势。'), Document(id='97fb659a-dc71-4642-bb3d-1326275a9ca4', metadata={'source': 'https://gyxxh.tj.gov.cn/glllm/gabsycs/gxdtgh/202504/t20250408_6902492.html'}, page_content='来源：\\n          天津市工业和信息化局\\n        \\n发布时间：\\n          2025-04-08 09:30\\n        \\n\\n\\n为深入贯彻党的二十届三中全会精神，落实《工业和信息化部办公厅关于开展中小企业出海服务专项行动的通知》及《工业和信息化部等17部门办公厅（室）关于开展2025年“一起益企”中小企业服务行动的通知》要求，进一步提升我市中小企业国际化水平和核心竞争力，助力中小企业开拓国际市场、增强风险防控能力，天津市中小企业服务中心于近日成功举办轻工产业链中小企业出海对接交流会。中国中小企业发展促进中心、天津市贸促会、天津市食品工业协会、天津市自行车电动车行业协会、武清区崔黄口镇商会，以及来自自行车、食品、地毯等领域的生产制造企业和进出口企业等20余家单位代表参加了活动。 \\u2002会上，国家中心产业集群发展研究所负责同志围绕中小企业“走出去”万帆耘海行动的实施背景、主要任务及具体进展作了专题报告，深入解读了当前中小企业出海的趋势、政策支持及发展机遇，为参会企业提供了权威的政策指导和行动路径。市贸促会会展服务中心相关负责同志重点介绍了贸促会组织的境外展会项目及我市境外参展补贴政策，并结合热点国家的贸易环境、市场动态及政策导向，为企业提供了详实的出海信息及参展支持服务，助力企业精准把握国际市场机遇。活动主题引起与会企业的高度关注，现场交流、对接气氛热烈。近年来，在国家大力推进构建以国内大循环为主体、国内国际双循环相互促进的新发展格局背景下，越来越多的中小企业将海外业务视为推动企业增长的重要战略。但在全球化经营过程中，中小企业面临诸多挑战，由于缺乏出海的实战经验，境外抗风险能力较低，减缓了我市企业出海的步伐。市中小企业服务中心2024年起已着手为有出海需求的企业搭建资源共享、经验互通的平台；2025年中心启动的“中小企业出海系列活动”涵盖出海对接交流会、国际市场调研、境外展会参展支持等多元化服务。下一步，中心将聚焦中小企业出海需求，深化与各行业协会及专业服务机构的协同合作，完善中小企业出海服务体系，整合资源系统化提供政策解读、市场开拓、国际人才引进、管理提升、跨境金融、权益保护、数字化转型及检测认证等关键节点的服务对接，助力企业提升国际化能力。推动我市中小企业在全球市场中形成竞争新优势。'), Document(id='cb23ba32-ad51-419d-b3ea-5af544b1208b', metadata={'source': 'https://gyxxh.tj.gov.cn/glllm/gabsycs/gxdtgh/202504/t20250408_6902492.html', 'start_index': 92}, page_content='来源：\\n          天津市工业和信息化局\\n        \\n发布时间：\\n          2025-04-08 09:30\\n        \\n\\n\\n为深入贯彻党的二十届三中全会精神，落实《工业和信息化部办公厅关于开展中小企业出海服务专项行动的通知》及《工业和信息化部等17部门办公厅（室）关于开展2025年“一起益企”中小企业服务行动的通知》要求，进一步提升我市中小企业国际化水平和核心竞争力，助力中小企业开拓国际市场、增强风险防控能力，天津市中小企业服务中心于近日成功举办轻工产业链中小企业出海对接交流会。中国中小企业发展促进中心、天津市贸促会、天津市食品工业协会、天津市自行车电动车行业协会、武清区崔黄口镇商会，以及来自自行车、食品、地毯等领域的生产制造企业和进出口企业等20余家单位代表参加了活动。 \\u2002会上，国家中心产业集群发展研究所负责同志围绕中小企业“走出去”万帆耘海行动的实施背景、主要任务及具体进展作了专题报告，深入解读了当前中小企业出海的趋势、政策支持及发展机遇，为参会企业提供了权威的政策指导和行动路径。市贸促会会展服务中心相关负责同志重点介绍了贸促会组织的境外展会项目及我市境外参展补贴政策，并结合热点国家的贸易环境、市场动态及政策导向，为企业提供了详实的出海信息及参展支持服务，助力企业精准把握国际市场机遇。活动主题引起与会企业的高度关注，现场交流、对接气氛热烈。近年来，在国家大力推进构建以国内大循环为主体、国内国际双循环相互促进的新发展格局背景下，越来越多的中小企业将海外业务视为推动企业增长的重要战略。但在全球化经营过程中，中小企业面临诸多挑战，由于缺乏出海的实战经验，境外抗风险能力较低，减缓了我市企业出海的步伐。市中小企业服务中心2024年起已着手为有出海需求的企业搭建资源共享、经验互通的平台；2025年中心启动的“中小企业出海系列活动”涵盖出海对接交流会、国际市场调研、境外展会参展支持等多元化服务。下一步，中心将聚焦中小企业出海需求，深化与各行业协会及专业服务机构的协同合作，完善中小企业出海服务体系，整合资源系统化提供政策解读、市场开拓、国际人才引进、管理提升、跨境金融、权益保护、数字化转型及检测认证等关键节点的服务对接，助力企业提升国际化能力。推动我市中小企业在全球市场中形成竞争新优势。'), Document(id='cda2301b-c67f-48c1-afe1-7e6e950e728d', metadata={'source': 'https://gyxxh.tj.gov.cn/glllm/gabsycs/gxdtgh/202504/t20250408_6902492.html'}, page_content='工信动态\\n          \\n\\n\\n\\n\\n\\n        天津市中小企业服务中心成功举办轻工 产业链中小企业出海对接交流会\\n      \\n\\n来源：\\n          天津市工业和信息化局\\n        \\n发布时间：\\n          2025-04-08 09:30')]}}\n",
      "\n",
      "----------------\n",
      "\n",
      "{'generate': {'answer': '参加活动的企业约有20余家。这些企业来自自行车、食品、地毯等领域，包括生产制造企业和进出口企业。活动由天津市中小企业服务中心主办，得到了多个行业协会和机构的支持。'}}\n",
      "\n",
      "----------------\n",
      "\n"
     ]
    }
   ],
   "source": [
    "for step in graph.stream(\n",
    "    {\"question\": \"有多少家企业参加活动?\"}, stream_mode=\"updates\"\n",
    "):\n",
    "    print(f\"{step}\\n\\n----------------\\n\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "53806dc3-49a3-4724-93f7-bc8bc2ffe247",
   "metadata": {},
   "source": [
    "### 流式传输 Token（Stream Tokens）："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "03c372d4-495a-483d-a6f7-95cf51cb9891",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "约|有|2|0余家企业参加了|此次活动。||"
     ]
    }
   ],
   "source": [
    "for message, metadata in graph.stream(\n",
    "    {\"question\": \"有多少家企业参加活动?\"}, stream_mode=\"messages\"\n",
    "):\n",
    "    print(message.content, end=\"|\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "343c9cc6-131b-4509-bfe4-b331ac7d91cd",
   "metadata": {},
   "source": [
    "## 完整例子"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "5faee1aa-e2a0-46b8-b027-c975d7ccfefc",
   "metadata": {},
   "outputs": [],
   "source": [
    "import bs4\n",
    "from langchain import hub\n",
    "from langchain_community.document_loaders import WebBaseLoader\n",
    "from langchain_core.documents import Document\n",
    "from langchain_text_splitters import RecursiveCharacterTextSplitter\n",
    "from langgraph.graph import START, StateGraph\n",
    "from typing_extensions import List, TypedDict\n",
    "\n",
    "# Load and chunk contents of the blog\n",
    "loader = WebBaseLoader(\n",
    "    web_paths=(\"https://gyxxh.tj.gov.cn/glllm/gabsycs/gxdtgh/202504/t20250408_6902492.html\",),\n",
    "    bs_kwargs=dict(\n",
    "        parse_only=bs4.SoupStrainer(\n",
    "            class_=(\"view-wrap\")\n",
    "        )\n",
    "    ),\n",
    ")\n",
    "docs = loader.load()\n",
    "\n",
    "text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)\n",
    "all_splits = text_splitter.split_documents(docs)\n",
    "\n",
    "# Index chunks\n",
    "_ = vector_store.add_documents(documents=all_splits)\n",
    "\n",
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "\n",
    "from langchain_core.prompts import PromptTemplate\n",
    "\n",
    "template = \"\"\"Use the following pieces of context to answer the question at the end.\n",
    "If you don't know the answer, just say that you don't know, don't try to make up an answer.\n",
    "Use three sentences maximum and keep the answer as concise as possible.\n",
    "Always say \"thanks for asking!\" at the end of the answer.\n",
    "\n",
    "{context}\n",
    "\n",
    "Question: {question}\n",
    "\n",
    "Helpful Answer:\"\"\"\n",
    "prompt = PromptTemplate.from_template(template)\n",
    "\n",
    "# Define state for application\n",
    "class State(TypedDict):\n",
    "    question: str\n",
    "    context: List[Document]\n",
    "    answer: str\n",
    "\n",
    "\n",
    "# Define application steps\n",
    "def retrieve(state: State):\n",
    "    retrieved_docs = vector_store.similarity_search(state[\"question\"])\n",
    "    return {\"context\": retrieved_docs}\n",
    "\n",
    "\n",
    "def generate(state: State):\n",
    "    docs_content = \"\\n\\n\".join(doc.page_content for doc in state[\"context\"])\n",
    "    messages = prompt.invoke({\"question\": state[\"question\"], \"context\": docs_content})\n",
    "    response = llm.invoke(messages)\n",
    "    return {\"answer\": response.content}\n",
    "\n",
    "\n",
    "# Compile application and test\n",
    "graph_builder = StateGraph(State).add_sequence([retrieve, generate])\n",
    "graph_builder.add_edge(START, \"retrieve\")\n",
    "graph = graph_builder.compile()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "id": "df29941d-f0bf-48f2-a430-0fee26cdcde6",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "约20家企业代表参加了活动。  \n",
      "thanks for asking!\n"
     ]
    }
   ],
   "source": [
    "response = graph.invoke({\"question\": \"有多少家企业参加活动?\"})\n",
    "print(response[\"answer\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5792709d-9eae-4729-8b97-700595f3bb87",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
